﻿// by Shimohana
using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.Geometries;

namespace AzukiMap.MapSource
{
    /// <summary>
    /// Edit Vector Mode
    /// </summary>
    public enum EditVectorType
    {
        Node,
        Line,
        Polygon,
        Rectangle
    }

    public enum EditVectorMode
    {
        NoEdit,
        Create,
        Edit,
        Move,
        Remove
    }

    public enum EditFeatureType
    {
        Feature,
        EditPoint,
        EditLine,
        EditPolygon,
        DivPoint
    }

    public class EditVectorData : LocalVectorData
    {
        public EditFeatureType EditFeatureType;
        public bool IsDivPoint;
    }

    /// <summary>
    /// Vector source for edit feature mode.
    /// </summary>
    public class EditVectorSource : LocalVectorSource
    {
        /// <summary>
        /// current id for feature. (auto number)
        /// </summary>
        private int currentId;

        private IGeometry workGeometry;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="type"></param>
        public EditVectorSource(VectorSourceTypes type)
            : base(type)
        {
            Init();
        }

        public EditVectorSource(string name, VectorSourceTypes vectorType, int srid)
            : base(name, vectorType, srid)
        {
            Init();
        }

        public EditVectorType EditVectorType { get; set; }
        public EditVectorMode EditVectorMode { get; set; }

        /// <summary>
        /// Vectors for drawing.
        /// </summary>
        public override List<LocalVectorData> Items
        {
            get
            {
                var list = new List<LocalVectorData>();

                // Ordering is important.
                if (EditPolygon != null) list.Add(EditPolygon);
                if (EditLine != null) list.Add(EditLine);

                list.AddRange(GetPoints());

                if (EditPoint != null) list.Add(EditPoint);

                return list;
            }
            set
            {
                //Points = value;
            }
        }

        public List<EditVectorData> Points { get; set; }

        public IGeometry WorkGeometry
        {
            get { return workGeometry; }
            set { workGeometry = value; }
        }

        public EditVectorData EditPoint { get; set; }

        public EditVectorData EditPolygon { get; set; }
        public EditVectorData EditLine { get; set; }

        /// <summary>
        /// Get next feature id.
        /// </summary>
        /// <returns></returns>
        public int GetNextId()
        {
            lock (this)
            {
                return ++currentId;
            }
        }

        /// <summary>
        /// Get current feature id.
        /// </summary>
        /// <returns></returns>
        public int GetCurrentId()
        {
            return currentId;
        }


        private List<LocalVectorData> GetPoints()
        {
            if (EditVectorMode != EditVectorMode.Create &&
                EditVectorMode != EditVectorMode.Edit)
            {
                return new List<LocalVectorData>();
            }

            var srcList = new List<EditVectorData>();
            var divList = new List<EditVectorData>();
            srcList.AddRange(GetPointsWithoutDiv());

            if (srcList.Count >= 2 && EditVectorMode == EditVectorMode.Edit)
            {
                bool isNeedLastAdd = false;
                if (EditPoint == null)
                {
                    if (srcList.Count > 2)
                    {
                        isNeedLastAdd = true;
                    }
                }

                Point geom;
                int count = srcList.Count - 1;
                if (isNeedLastAdd) count++;
                for (int i = 0; i < count; i++)
                {
                    if (isNeedLastAdd && i == count - 1)
                    {
                        geom = GetDivPoint(srcList[i].Geometry, srcList[0].Geometry);
                    }
                    else
                    {
                        geom = GetDivPoint(srcList[i].Geometry, srcList[i + 1].Geometry);
                    }
                    if (geom != null)
                    {
                        var data = new EditVectorData();
                        data.Geometry = geom;
                        data.Id = GetNextId();
                        data.EditFeatureType = EditFeatureType.DivPoint;
                        divList.Add(data);
                    }
                }
            }

            Points = new List<EditVectorData>();
            for (int i = 0; i < srcList.Count; i++)
            {
                Points.Add(srcList[i]);
                if (i < divList.Count)
                {
                    Points.Add(divList[i]);
                }
            }

            var list = new List<LocalVectorData>();
            for (int i = 0; i < Points.Count; i++)
            {
                list.Add(Points[i]);
            }

            return list;
        }

        private Point GetDivPoint(IGeometry g1, IGeometry g2)
        {
            var p1 = g1 as IPoint;
            var p2 = g2 as IPoint;
            if (p1 == null || p2 == null) return null;
            return new Point(
                (p1.X + p2.X)/2, (p1.Y + p2.Y)/2
                );
        }

        public List<EditVectorData> GetPointsWithoutDiv()
        {
            var list = new List<EditVectorData>();
            foreach (EditVectorData data in Points)
            {
                if (data.EditFeatureType == EditFeatureType.DivPoint) continue;
                list.Add(data);
            }
            return list;
        }

        // Points of feature.(only fixed)

        public IGeometry GetGeometry()
        {
            return workGeometry;
        }

        public void SetGeometry(IGeometry geom)
        {
            Clear();

            if (geom is IPoint)
            {
                var p = geom as IPoint;
                AddPoint(p.X, p.Y);
            }
            else if (geom is ILineString)
            {
                var line = geom as ILineString;
                foreach (ICoordinate c in line.Coordinates)
                {
                    AddPoint(c.X, c.Y);
                }
            }
            else if (geom is IPolygon)
            {
                var poly = geom as IPolygon;
                foreach (ICoordinate c in poly.Coordinates)
                {
                    AddPoint(c.X, c.Y);
                }
            }

            if (Points.Count > 1)
            {
                var p1 = Points[0].Geometry as IPoint;
                var p2 = Points[Points.Count - 1].Geometry as IPoint;
                if (p1.X == p2.X && p1.Y == p2.Y) RemovePoint(Points[Points.Count - 1]);
            }

            SetWorkFeature();
        }

        public void MovePoints(double x, double y)
        {
            foreach (EditVectorData data in Points)
            {
                var point = data.Geometry as IPoint;
                if (point == null) continue;

                point.X += x;
                point.Y += y;
            }
        }

        public void SetWorkFeature()
        {
            var list = new List<EditVectorData>();
            workGeometry = null;

            list.AddRange(GetPointsWithoutDiv());
            if (EditPoint != null) list.Add(EditPoint);

            if (list.Count == 0 )
            {
                EditPoint = null;
                EditLine = null;
                EditPolygon = null;
            }
            else if (list.Count == 1)
            {
                if ( EditVectorType == EditVectorType.Node)
                {
                    EditPoint = new EditVectorData
                                    {
                                        Geometry = (Point) list[0].Geometry
                                    };
                }
            }
            else if (list.Count == 2)
            {
                var p1 = (Point) list[0].Geometry;
                var p2 = (Point) list[1].Geometry;

                if (EditVectorType == EditVectorType.Polygon
                    || EditVectorType == EditVectorType.Line)
                {
                    EditPolygon = null;

                    Geometry geom =
                        new LineString(
                            new[]
                                {
                                    new Coordinate(p1.X, p1.Y),
                                    new Coordinate(p2.X, p2.Y),
                                }
                            );

                    if (EditLine != null)
                    {
                        EditLine.Geometry = geom;
                    }
                    else
                    {
                        var data = new EditVectorData();
                        data.Geometry = geom;
                        data.Id = GetNextId();
                        data.EditFeatureType = EditFeatureType.EditLine;
                        EditLine = data;
                    }

                    workGeometry = geom;
                }

                else if (EditVectorType == EditVectorType.Rectangle)
                {
                    EditLine = null;

                    double minX = Math.Min(p1.X, p2.X);
                    double minY = Math.Min(p1.Y, p2.Y);
                    double maxX = Math.Max(p1.X, p2.X);
                    double maxY = Math.Max(p1.Y, p2.Y);

                    Geometry geom =
                        new Polygon(
                            new LinearRing(
                                new ICoordinate[]
                                    {
                                        new Coordinate(minX, minY),
                                        new Coordinate(minX, maxY),
                                        new Coordinate(maxX, maxY),
                                        new Coordinate(maxX, minY),
                                        new Coordinate(minX, minY)
                                    }
                                ));

                    if (EditPolygon != null)
                    {
                        EditPolygon.Geometry = geom;
                    }
                    else
                    {
                        var data = new EditVectorData();
                        data.Geometry = geom;
                        data.Id = GetNextId();
                        data.EditFeatureType = EditFeatureType.EditPolygon;
                        EditPolygon = data;
                    }

                    workGeometry = geom;
                }
            }

            else if (list.Count > 2)
            {
                EditLine = null;

                var ps = new List<ICoordinate>();
                foreach (LocalVectorData item in list)
                {
                    var p = (Point) item.Geometry;
                    ps.Add(new Coordinate(p.X, p.Y));
                }
                var ep = (Point) list[0].Geometry;
                ps.Add(new Coordinate(ep.X, ep.Y));

                Geometry geom =
                    new Polygon(
                        new LinearRing(
                            ps.ToArray())
                        );

                if (EditPolygon != null)
                {
                    EditPolygon.Geometry = geom;
                }
                else
                {
                    var data = new EditVectorData();
                    data.Geometry = geom;
                    data.Id = GetNextId();
                    data.EditFeatureType = EditFeatureType.EditPolygon;
                    EditPolygon = data;
                }

                workGeometry = geom;
            }
        }

        // editing point

        protected override void Init()
        {
            EditVectorType = EditVectorType.Polygon;

            Points = new List<EditVectorData>();
            EditPoint = null;
            EditPolygon = null;
            EditLine = null;
        }

        public void AddPoint(double x, double y)
        {
            Geometry geom =
                new Point(
                    new Coordinate(x, y)
                    );

            var data = new EditVectorData();
            data.Geometry = geom;
            data.Id = GetNextId();
            data.EditFeatureType = EditFeatureType.EditPoint;
            Points.Add(data);
        }

        public void SetEditPoint(double x, double y)
        {
            Geometry geom =
                new Point(
                    new Coordinate(x, y)
                    );

            if (EditPoint != null)
            {
                EditPoint.Geometry = geom;
            }
            else
            {
                var data = new EditVectorData();
                data.Geometry = geom;
                data.Id = GetNextId();
                data.EditFeatureType = EditFeatureType.EditPoint;
                EditPoint = data;
            }
        }

        public void RemovePoint(EditVectorData item)
        {
            Points.Remove(item);
        }

        public void SetWorkPoint(EditVectorData item)
        {
            item.Id = GetNextId();
            EditPoint = item;
        }

        public override void Add(LocalVectorData item)
        {
            Points.Add((EditVectorData) item);
        }

        public override void Remove(LocalVectorData item)
        {
            Points.Remove((EditVectorData) item);
        }

        public override void Clear()
        {
            Points = new List<EditVectorData>();
            EditPoint = null;
            EditPolygon = null;
            EditLine = null;
        }
    }
}